extends CharacterBody3D

class_name GameCharacter

@export var walk_speed = 5.0
@export var sprint_speed = 8.0
@export var crouch_speed = 3.0

@export var mouse_sens = 0.2
@export var lean_length = .45
@export var lean_angle = 20

@export var can_ledge_grab = true
@export var can_lean = true

@export var inventory: InventoryData

@onready var neck = $neck
@onready var head = $neck/head
@onready var camera_3d = $neck/head/Camera3D
@onready var right_shoulder: RayCast3D = $right_shoulder
@onready var left_shoulder: RayCast3D = $left_shoulder

@onready var stand_collision = $stand_collision
@onready var crouch_collision = $crouch_collision
@onready var crouch_ray = $crouch_ray
@onready var grab_ray = $grab_ray
@onready var grab_space_ray = $grab_space_ray

@onready var step_over_ray: RayCast3D = $step_over_ray
@onready var step_height_ray: RayCast3D = $step_height_ray
@onready var floor_probe: RayCast3D = $floor_probe

@onready var interaction_ray: RayCast3D = $neck/head/Camera3D/interaction_ray

const SPEED = 5.0
const JUMP_VELOCITY = 5.5
const LERP_SPEED = 7

const STAND_NECK_HEIGHT = 1.6
const CROUCH_NECK_HEIGHT = 0.8

const LEAN_CAMERA_OFFSET = .1

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var speed = walk_speed

# grab vars and consts
const GRAB_WINDOW = .5

var is_grabbing = false
var grab_timer = 0
var grab_timeout = false

var can_sprint =  true

var is_crouching = false

var ignore_input = false

signal toggle_inventory()

#func  _ready() -> void:
	#if not inventory:
		#pass
		#inventory = new InventoryData()

func _input(event):
	if ignore_input:
		return
	if event is InputEventMouseMotion:
		#if Input.is_action_pressed("f")
		if is_grabbing || Input.is_action_pressed("free_look"):
			neck.rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
		else:
			rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
		head.rotate_x(deg_to_rad(-event.relative.y * mouse_sens))
		head.rotation.x = clamp(head.rotation.x, deg_to_rad(-89), deg_to_rad(89))

func _physics_process(delta):
	if Input.is_action_just_pressed("inventory"):
		toggle_inventory.emit()
	# Handle crouch
	if Input.is_action_pressed("crouch"):
		if is_on_floor():
			speed = crouch_speed
		neck.position.y = lerp(neck.position.y, CROUCH_NECK_HEIGHT, LERP_SPEED * delta)
		stand_collision.disabled = true
		crouch_collision.disabled = false
		can_sprint = false
	# Prevent from standing up if something above blocks full height.
	elif !crouch_ray.is_colliding():
		neck.position.y = lerp(neck.position.y, STAND_NECK_HEIGHT, LERP_SPEED * delta)
		stand_collision.disabled = false
		crouch_collision.disabled = true
		can_sprint = true
	
	# Handle sprint
		if is_on_floor():
			can_sprint = true
		else:
			can_sprint = false
		if Input.is_action_pressed("sprint"):
			if can_sprint:
				speed = sprint_speed
			else:
				speed = walk_speed
		else:
			speed = walk_speed
	
	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") && ((is_on_floor() && !crouch_ray.is_colliding()) || is_grabbing):
		velocity.y = JUMP_VELOCITY
		if is_grabbing:
			grab_timeout = true
			grab_timer = 0.0
	# Handle leans
	# TODO make smooth angle, remove jank, simplify code
	if Input.is_action_pressed("lean_left"):
		if left_shoulder.is_colliding():
			var angle_coefficient = abs(
				to_local(left_shoulder.get_collision_point()).x / lean_length
			)
			neck.position.x = lerp(neck.position.x, to_local(left_shoulder.get_collision_point()).x + LEAN_CAMERA_OFFSET, LERP_SPEED * delta)
			camera_3d.rotation.z = lerp(camera_3d.rotation.z, deg_to_rad(lean_angle * angle_coefficient), LERP_SPEED * delta)
		else:
			neck.position.x = lerp(neck.position.x, -lean_length, LERP_SPEED * delta)
			camera_3d.rotation.z = lerp(camera_3d.rotation.z, deg_to_rad(lean_angle), LERP_SPEED * delta)
	elif Input.is_action_pressed("lean_right"):
		if right_shoulder.is_colliding():
			var angle_coefficient = abs(
				to_local(right_shoulder.get_collision_point()).x / lean_length
			)
			neck.position.x = lerp(neck.position.x, to_local(right_shoulder.get_collision_point()).x - LEAN_CAMERA_OFFSET, LERP_SPEED * delta)
			camera_3d.rotation.z = lerp(camera_3d.rotation.z, deg_to_rad(-lean_angle * angle_coefficient), LERP_SPEED * delta)
		else:
			neck.position.x = lerp(neck.position.x, lean_length, LERP_SPEED * delta)
			camera_3d.rotation.z = lerp(camera_3d.rotation.z, deg_to_rad(-lean_angle), LERP_SPEED * delta)
	else:
		neck.position.x = lerp(neck.position.x, 0.0, LERP_SPEED * delta)
		camera_3d.rotation.z = lerp(camera_3d.rotation.z, deg_to_rad(0), LERP_SPEED * delta)

	# Handle grab
	if (
		can_ledge_grab && 
		!Input.is_action_pressed("crouch") && 
		grab_ray.is_colliding() && (
			!grab_space_ray.is_colliding() 
			|| (
				grab_ray.get_collider_rid() != grab_space_ray.get_collider_rid()
				)
			)
		):
		if !grab_timeout:
			velocity.y = 0
		is_grabbing = true
	elif step_over_ray.is_colliding() && !step_height_ray.is_colliding() && is_on_floor():
		#if step_over_ray.get_collider_rid() != floor_probe.get_collider_rid():
		position.y = lerp(position.y, step_over_ray.get_collision_point().y, LERP_SPEED*delta*2)
		position.y = step_over_ray.get_collision_point().y
		velocity.y = 0
	elif !is_on_floor():
		velocity.y -= gravity * delta
		is_grabbing = false
	#print('step_over_ray: %s, step_height_ray: %s, is_on_floor: %s' % [step_over_ray.is_colliding() , !step_height_ray.is_colliding() , is_on_floor()])
	if grab_timeout:
		grab_timer += delta
		if grab_timer > GRAB_WINDOW:
			grab_timeout = false
	
	var input_dir = Input.get_vector("left", "right", "forward", "back")
	var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	
	if direction:
		velocity.x = direction.x * speed
		velocity.z = direction.z * speed
	else:
		velocity.x = move_toward(velocity.x, 0, speed)
		velocity.z = move_toward(velocity.z, 0, speed)
	if !Input.is_action_pressed("free_look") && !is_grabbing:
		rotation.y = neck.global_rotation.y
		neck.rotation.y = 0
	
	if interaction_ray.is_colliding():
		if Input.is_action_just_pressed("interact"):
			var collider1 = interaction_ray.get_collider().get_parent()
			var collider2 = collider1.get_parent()
			if collider1 is InteractiveObject3D:
				collider1.interact()
			elif collider2 is InteractiveObject3D:
				collider2.interact()
	move_and_slide()
